package com.baomibing.flow.enginee;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.baomibing.core.common.Assert;
import com.baomibing.core.common.CollectionMapperDecorator;
import com.baomibing.core.exception.ServerRuntimeException;
import com.baomibing.flow.constant.ExecutionTypeEnum;
import com.baomibing.flow.constant.NodeTypeEnum;
import com.baomibing.flow.dto.*;
import com.baomibing.flow.exception.FlowExceptionEnum;
import com.baomibing.flow.execute.Execution;
import com.baomibing.flow.json.JEnd;
import com.baomibing.flow.json.JStart;
import com.baomibing.flow.model.*;
import com.baomibing.flow.service.*;
import com.baomibing.flow.track.NodeHistory;
import com.baomibing.flow.track.ProcessTrack;
import com.baomibing.flow.transfer.WorkFlowTransfer;
import com.baomibing.flow.ui.UViewPort;
import com.baomibing.tool.constant.Formats;
import com.baomibing.tool.constant.Strings;
import com.baomibing.tool.util.Checker;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.EnumUtils;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * WorkFlowEngine
 *
 * @author frog 2023/9/19 09:27
 * @version 1.0.0
 **/
@Component
public class WorkFlowEngine {

    @Autowired private SysFlowService flowService;
    @Autowired private SysFlowInstanceService instanceService;
    @Autowired private SysFlowTaskService taskService;
    @Autowired private SysFlowTrackService trackService;
    @Autowired private SysFlowHistoryInstanceService historyInstanceService;
    @Autowired private SysFlowHistoryTaskService historyTaskService;
    @Autowired private CollectionMapperDecorator collectionMapperDecorator;


    public String deploy(WorkFlow flow) {
        String id;
        if (Checker.beEmpty(flow.getId())) {
            SysFlowDto v = new SysFlowDto();
            v.setName(flow.getName()).setStyle(flow.getStyle())
                    .setStartNode(JSONObject.toJSONString(flow.getStart()))
                    .setProcesses(JSONArray.toJSONString(flow.getProcesses(), SerializerFeature.DisableCircularReferenceDetect));
            flowService.saveFlow(v);
            id = v.getId();
        } else {
            SysFlowDto dbFlow = flowService.getFlow(flow.getId());
            dbFlow.setProcesses(JSONArray.toJSONString(flow.getProcesses()))
                    .setStartNode(JSONObject.toJSONString(flow.getStart()))
                    .setStyle(flow.getStyle());
            flowService.updateFlow(dbFlow);
            id = dbFlow.getId();
        }

        return id;
    }

    public void saveOrUpdateTrack(String instanceId, List<ProcessTrack> tracks) {
        Assert.CheckArgument(instanceId);
        Assert.CheckArgument(tracks);
        trackService.deleteByInstance(instanceId);
        List<SysFlowTrackDto> list = Lists.newArrayList();
        for (ProcessTrack pt : tracks) {
            SysFlowTrackDto ft = new SysFlowTrackDto();
            ft.setStep(pt.getLevel()).setActor(pt.getActor()).setInstanceId(instanceId).setTrackType(pt.getExecuteType().name())
                .setNode(JSONObject.toJSONString(pt.getNode())).setSource(JSONArray.toJSONString(pt.getSources()))
                .setTarget(JSONArray.toJSONString(pt.getTargets()));
            list.add(ft);
        }
        trackService.saveTracks(list);
    }

    private String now2String() {
        return new DateTime().toString(Formats.TIME_STAMP_FORMAT);
    }

    public WorkFlow xflow2WorkFlow(SysFlowDto xflow) {
        //反序列化生成workflow
        WorkFlow flow = new WorkFlow();
        Map<String, BaseNode> nodeMap = flow.deserialMap(xflow.getNodes());
        List<Process> processes = flow.deserialize(nodeMap, xflow.getProcesses());

        JStart start = JSONObject.parseObject(xflow.getStartNode(), JStart.class);
        JEnd end = JSONObject.parseObject(xflow.getEndNode(), JEnd.class);
        flow.setProcesses(processes)
                .setStart((StartNode) nodeMap.get(start.getName()))
                .setEnd((EndNode) nodeMap.get(end.getName()))
                .setName(xflow.getName()).setStyle(xflow.getStyle())
                .setBeComplete(flow.getBeComplete())
                .setViewPort(JSONObject.parseObject(xflow.getViewPort(), UViewPort.class));
        return flow;
    }

    public WorkFlow loadFlow(String instanceId) {
        SysFlowInstanceDto instance = instanceService.getInstance(instanceId);
        if (Checker.beNull(instance)) {
            throw new ServerRuntimeException(FlowExceptionEnum.NOT_FOUND_THE_ID_OF_INSTANCE, instanceId);
        }

        WorkFlow flow = new WorkFlow();

        Map<String, BaseNode> nodeMap = flow.deserialMap(instance.getNodes());
        List<Process> processes = flow.deserialize(nodeMap, instance.getProcesses());

        JStart start = JSONObject.parseObject(instance.getStartNode(), JStart.class);
        JEnd end = JSONObject.parseObject(instance.getEndNode(), JEnd.class);
        flow.setProcesses(processes)
                .setStart((StartNode) nodeMap.get(start.getName()))
                .setEnd((EndNode) nodeMap.get(end.getName()))
                .setName(instance.getName()).setStyle(instance.getStyle())
                .setBeComplete(flow.getBeComplete())
                .setViewPort(JSONObject.parseObject(instance.getViewPort(), UViewPort.class));
        return flow;

    }


    private void doUpdateInstance(WorkFlow flow) {
        String instanceId = flow.getInstanceId();
        Assert.CheckArgument(instanceId);
        SysFlowInstanceDto instance = instanceService.getInstance(instanceId);
        String process = JSONArray.toJSONString(flow.getProcesses(), SerializerFeature.DisableCircularReferenceDetect);
        instance.setProcesses(process).setBeComplete(flow.getBeComplete());
        instanceService.updateInstance(instance);
        SysFlowHistoryInstanceDto historyInstance = historyInstanceService.getHistoryInstance(instanceId);
        historyInstance.setProcesses(process).setBeComplete(flow.getBeComplete());
        historyInstanceService.updateHistoryInstance(historyInstance);

    }


    public String doStartFlow(String flowId, Execution execution) {
        SysFlowDto xflow = flowService.getFlow(flowId);
        if (Checker.beNull(xflow)) {
            throw new ServerRuntimeException(FlowExceptionEnum.NOT_FOUND_THE_ID_OF_FLOW, flowId);
        }

        WorkFlow flow = new WorkFlow();
        WorkFlowTransfer transfer = new WorkFlowTransfer(flow);
        flow.setProcesses(transfer.processDeserialize(xflow.getProcesses()))
                .setStart((StartNode) transfer2Node(transfer, xflow.getStartNode()))
                .setEnd((EndNode) transfer2Node(transfer, xflow.getEndNode()))
                .setName(xflow.getName()).setStyle(xflow.getStyle());

        SysFlowInstanceDto instance = new SysFlowInstanceDto();

        instance.setFlowId(flowId).setBeComplete(false).setName(execution.getActor() + now2String() + flow.getName());
        flow.doStart(execution);
        instance.setProcesses(JSONArray.toJSONString(flow.getProcesses(), SerializerFeature.DisableCircularReferenceDetect)).setBeComplete(flow.getBeComplete())
                .setName(xflow.getName()).setStartNode(xflow.getStartNode()).setStyle(xflow.getStyle());
        instanceService.saveInstance(instance);

        //update history instance
        SysFlowHistoryInstanceDto historyInstance = collectionMapperDecorator.map(instance, SysFlowHistoryInstanceDto.class);
        historyInstanceService.saveHistoryInstance(historyInstance);


        flow.setInstanceId(instance.getId());
        doUpdateTask(flow);
        saveOrUpdateTrack(instance.getId(), flow.getTracks());
        return instance.getId();
    }


    public Class<? extends WorkNode> classOfNodeType(String nodeType) {
        NodeTypeEnum type = EnumUtils.getEnum(NodeTypeEnum.class, nodeType);
        return type.getTypeOfClass();
    }

    public String processSerialize(List<Process> processes) {
        if (Checker.beEmpty(processes)) {
            return Strings.EMPTY;
        }
        return JSONArray.toJSONString(processes, SerializerFeature.DisableCircularReferenceDetect);
    }


    public WorkNode transfer2Node(WorkFlowTransfer transfer, String node) {
        final String NAME = "name";
        JSONObject jsonNode = JSONObject.parseObject(node);
        return transfer.getNodeByName(jsonNode.getString(NAME));
    }

    public List<WorkNode> transfer2Nodes(WorkFlowTransfer transfer, String nodes) {
        final String NAME = "name";
        JSONArray arr = JSONArray.parseArray(nodes);
        List<WorkNode> list = Lists.newArrayList();
        for (int i = 0, len = arr.size(); i < len; i++) {
            JSONObject jsonNode = arr.getJSONObject(i);
            list.add(transfer.getNodeByName(jsonNode.getString(NAME)));
        }
        return list;
    }


    public void doUpdateTask(WorkFlow flow) {
        Assert.CheckArgument(flow);
        Assert.CheckArgument(flow.getInstanceId());
        if (!flow.getBeExecuteProcess()) {
            return;
        }

        //executing task complete
        List<SysFlowTaskDto> executingList = taskService.listExecutingTaskByInstance(flow.getInstanceId());
        int step = Checker.beEmpty(executingList) ? 0 : executingList.get(0).getStep();
        List<NodeHistory> histories = flow.getExecuteOrders().stream().filter(e -> e.getStep() > step).collect(Collectors.toList());
        Map<String, NodeHistory> nodeMap  = flow.getExecuteOrders().stream().filter(t -> Checker.beNotEmpty(t.getTask().getId())).collect(Collectors.toMap(t -> t.getTask().getId(), Function.identity()));

        boolean beChange = false;
        for (SysFlowTaskDto t : executingList) {
            NodeHistory history = nodeMap.get(t.getId());
            if (Checker.beNull(history)) {
                continue;
            }
            TaskNode node = history.getTask();
            t.setState(ExecutionTypeEnum.COMPLETED.name()).setActor(history.getActor()).setExecuteTime(history.getDate())
                    .setBeAllActionReject(node.getBeAllActionReject()).setBeAllActionComplete(node.getBeAllActionComplete());
            beChange = true;
        }

        if (Checker.beNotEmpty(executingList) && beChange) {
            taskService.updateTasks(executingList);
        }


        int maxStep = histories.stream().mapToInt(NodeHistory::getStep).max().orElse(0) + 1;
        List<SysFlowTaskDto> needAdds = Lists.newArrayList();
        for (NodeHistory h : histories) {
            TaskNode tn = h.getTask();
            SysFlowTaskDto task = new SysFlowTaskDto();
            task.setName(tn.getName()).setState(ExecutionTypeEnum.COMPLETED.name()).setStep(h.getStep())
                    .setActionType(tn.getActionType().name()).setEntrustType(tn.getEntrustType().name())
                    .setEntrusts(String.join(Strings.COMMA, tn.getEntrusts())).setBeAllActionComplete(tn.getBeAllActionComplete())
                    .setBeAllActionReject(tn.getBeAllActionReject()).setTaskType(tn.getTaskType().name())
                    .setInstanceId(flow.getInstanceId()).setActor(h.getActor()).setExecuteTime(h.getDate())
                    .setStyle(tn.getStyle()).setNodeId(tn.getNodeId());
            needAdds.add(task);
        }

        if (Checker.beNotEmpty(histories)) {
            taskService.saveTasks(needAdds);
        }

        ///save new executing task
        List<WorkNode> currents = flow.getCurrents();
        List<SysFlowTaskDto> newExecutingList = Lists.newArrayList();
        for (WorkNode node : currents) {
            TaskNode tn = (TaskNode)node;
            SysFlowTaskDto task = new SysFlowTaskDto();
            task.setName(node.getName()).setState(ExecutionTypeEnum.EXECUTING.name()).setStep(maxStep)
                    .setActionType(tn.getActionType().name()).setEntrustType(tn.getEntrustType().name())
                    .setEntrusts(String.join(Strings.COMMA, tn.getEntrusts())).setBeAllActionComplete(tn.getBeAllActionComplete())
                    .setBeAllActionReject(tn.getBeAllActionReject()).setTaskType(tn.getTaskType().name())
                    .setInstanceId(flow.getInstanceId())
                    .setStyle(tn.getStyle()).setVariables(JSONArray.toJSONString(tn.getVariables()));
            newExecutingList.add(task);
        }
        if (Checker.beNotEmpty(newExecutingList)) {
            taskService.saveTasks(newExecutingList);
        }

        //history
        List<SysFlowHistoryTaskDto> historyNeedAdds = Lists.newArrayList(
                collectionMapperDecorator.mapCollection(needAdds, SysFlowHistoryTaskDto.class));
        if (Checker.beNotEmpty(historyNeedAdds)) {
            historyTaskService.saveHistoryTasks(historyNeedAdds);
        }

    }



}
